import { HvigorNode, HvigorPlugin } from '@ohos/hvigor';
import ts, { Program, TypeNode } from "typescript";
import Handlebars from "handlebars";
import * as path from "path";
import {
    readFileSync,
    existsSync,
    mkdirSync,
    readdirSync,
    writeFileSync,
} from "fs";
import { config } from 'process';
import { get } from 'http';
import { json } from 'stream/consumers';

/**
 *  默认组件化输出路径
 */
const DEFAULT_OUTPUT_DIR = "src/main/resources/rawfile/tcg_output";

/**
 * 插件配置
 */
export class PluginConfig {

    /**
     * 模块名称
     */
    moduleName?: string;
    /**
     * 模块路径
     */
    modulePath?: string;

    /**
     * 扫描路径
     */
    scanDir?: string;
    /**
     * 输出路径
     */
    outputDir?: string;
}


/**
 * 组件化框架插件 入口
 * @returns HvigorPlugin
 */
export function tcgPlugin(pluginConfig: PluginConfig): HvigorPlugin {
    return {
        pluginId: 'TRPlugin',
        apply(node: HvigorNode) {
            pluginConfig.moduleName = node.getNodeName();
            pluginConfig.modulePath = node.getNodePath();
            if (!pluginConfig.outputDir) {
                pluginConfig.outputDir = DEFAULT_OUTPUT_DIR;
            }
            logger(`Plugin Config：\n ${JSON.stringify(pluginConfig, null, 4)}`)
            pluginExec(pluginConfig)
        }
    }
}

/**
 * 插件执行方法
 * @param pluginConfig 插件配置
 */
function pluginExec(pluginConfig: PluginConfig) {
    try {
        logger(`Plugin exec...\n  `)
        const scanPath = `${pluginConfig.modulePath}/${pluginConfig.scanDir}`;
        const files: string[] = readdirSync(scanPath);
        files.forEach((fileName) => {
            const sourcePath = `${scanPath}/${fileName}`;
            logger(`Scan sourcePath : ${sourcePath}`)
            let analyzer = new EtsAnalyzer(pluginConfig, sourcePath);
            analyzer.start();
        })
        generateComponentMap(pluginConfig);
    } finally {
        componentMap = {};
    }
    
}

/**
 * 生成 ComponentMap
 */
function generateComponentMap(pluginConfig: PluginConfig) {
    const jsonOutput = JSON.stringify(componentMap, null, 2);
    const outputDir = `${pluginConfig.modulePath}/${pluginConfig.outputDir}`;
    logger(`${outputDir} CompoentMap：\n${jsonOutput}`)
    if (!existsSync(outputDir)) {
        mkdirSync(outputDir, { recursive: true });
    }
    writeFileSync(`${outputDir}/${pluginConfig.moduleName}.json`, jsonOutput, {
        encoding: "utf8",
    });
}



interface ComponentModel {
    importPath: string;
    //行为
    methodName: string;
    actionVersion: string;
    //组件
    className: string;
    componentName: string;
    classVersion: string;

    paramTypes: string[];
    returnType: string;
}

interface FunctionParams {
    paramTypes: string[];
    returnType: string;
}

interface ComponentMap {
    [key: string]: ComponentModel;
}

/**
 * 组件映射实例
 */
let componentMap: ComponentMap = {};

/**
 * Ets 解析器
 */
export class EtsAnalyzer {

    sourcePath: string;
    pluginConfig: PluginConfig;
    program: Program;
    checker: ts.TypeChecker

    constructor(pluginConfig: PluginConfig, sourcePath: string) {
        this.pluginConfig = pluginConfig;
        this.sourcePath = sourcePath;
        this.program = ts.createProgram([this.sourcePath], {
            target: ts.ScriptTarget.ES2021,
            module: ts.ModuleKind.CommonJS,
        });
        this.checker = this.program.getTypeChecker();
    }

    start() {
        const sourceCode = readFileSync(this.sourcePath, "utf-8");
        // console.debug(sourceCode);
        const sourceFile = ts.createSourceFile(
            this.sourcePath,
            sourceCode,
            ts.ScriptTarget.ES2021,
            false
        );

        ts.forEachChild(sourceFile, (node: ts.Node) => {
            // console.debug(node);
            this.resolveNode(node);
        });
    }

    resolveNode(node: ts.Node) {
        //logger(`resolveNode : node.kind: ${node.kind}`)
        switch (node.kind) {
            case ts.SyntaxKind.ClassDeclaration:
                this.resolveClassDeclaration(node);
                break;
            // case ts.SyntaxKind.MethodDeclaration:
            //     this.resolveMethodDeclaration(node);
            //     break;
        }
    }

    /**
     * 
     * @param node 扫描类
     * @returns 
     */
    resolveClassDeclaration(node: ts.Node) {
        let classDecl = node as ts.ClassDeclaration;
        // logger(`classDecl: ${JSON.stringify(classDecl, null, 2)}`)
        let classDeclModifiers = classDecl.modifiers;
        if (!classDeclModifiers) {
            return;
        }


        //找到对应类名
        let classNameTem = this.getNameTextByIdentifier(classDecl.name);

        let collectComponentResult = {
            className: classNameTem,
            // 注解类型
            property: null as object | null
        }

        for (let i = 0; i < classDeclModifiers.length; i++) {
            const decorator = classDeclModifiers[i];
            let expressionText = this.getDecoratorString(decorator);
            //忽略非”TCGComponent“注解
            if (expressionText !== 'TCGComponent') {
                continue;
            }


            //this.analyzeResult.componentClass = className;
            let propertyTemp = this.getDecoratorProperty(decorator);
            collectComponentResult.property = propertyTemp

            logger(`类名 ${classNameTem} 装饰器属性： ${JSON.stringify(collectComponentResult)} `)

            //开始遍历方法
            let members = classDecl.members;
            if (!members) {
                continue;
            }

            for (let j = 0; j < members.length; j++) {
                const member = members[j];
                // logger(`member: ${JSON.stringify(member, null, 2)}`);
                this.resolveMethodDeclaration(collectComponentResult, member);
            }
        }
    }

    /**
     * 处理方法装饰器
     * @param node 节点
     * @returns 
     */
    resolveMethodDeclaration(collectComponentResult: any, node: ts.Node) {
        //方法声明
        let methodDecl = node as ts.MethodDeclaration;
       
        //方法的修饰器
        let methodDeclModifiers = methodDecl.modifiers;
        if (!methodDeclModifiers) {
            return;
        }

        // logger(`methodDeclModifiers ${JSON.stringify(methodDeclModifiers)}`)

        for (let i = 0; i < methodDeclModifiers.length; i++) {
            const decorator = methodDeclModifiers[i];
            let expressionText = this.getDecoratorString(decorator);
            //TCGAction
            if (expressionText !== 'TCGAction') {
                continue;
            }

            //找到对应类名
            let methodName = this.getNameTextByIdentifier(methodDecl.name);
            //this.analyzeResult.componentClass = className;
            let property = this.getDecoratorProperty(decorator)
            // logger(`方法名 ${methodName} 装饰器属性：${JSON.stringify(property)}`)
            // logger(`txy：${JSON.stringify(collectComponentResult)}`)
            this.checkForDuplicateKeys(property.name)
            let params: FunctionParams = this.getFunctionTypeInfo(methodDecl)
            
            componentMap[property.name] = {
                importPath: `${this.pluginConfig.moduleName}`,
                methodName: methodName,
                actionVersion: property.version,
                className: collectComponentResult.className,
                componentName: collectComponentResult.property.name,
                classVersion: collectComponentResult.property.version,
                paramTypes: params.paramTypes,
                returnType: params.returnType
            };

             logger(`txy：${JSON.stringify(componentMap)}`)
        }
    }

    // 获取方法名参数和返回参数
    getFunctionTypeInfo(node: ts.MethodDeclaration): FunctionParams {
        let paramTypeStrList: string[] = []
        let returnTypeStr = "";
        
        // 节点参数处理
        node.parameters.map((parameter) => {
            // 判断参数类型
            if (parameter.type) {
                paramTypeStrList.push(this.processTypeKind(parameter.type))
            } else {
                paramTypeStrList.push("")
            }
            
        })
        
        // 节点返回值处理
        if (node.type) {
            returnTypeStr = this.processTypeKind(node.type)
        }
    
        return {
          paramTypes: paramTypeStrList,
          returnType: returnTypeStr,
        };
    };

      /**
     * 根据 SyntaxKind 结果转换为实际值
     * @param node 
     */
    processTypeKind(node: TypeNode): string {
        logger(`txy node ${JSON.stringify(node)}`)
        let s = JSON.parse(JSON.stringify(node))
        let typeKind = s.kind
        let res = ""
        switch (typeKind) {
            case ts.SyntaxKind.TypeReference:
                // 自定义参数
                if (s.typeName.escapedText) {
                    res = s.typeName.escapedText
                }
                break
            default:
                // 系统参数
                let typeN = ts.SyntaxKind[typeKind]
                res = typeN.replace("Keyword", "").toLowerCase()
                break
        }
        return res
    }


    /**
     * 检查并抛出异常，如果在目标对象和源对象中发现重复的键
     * @param target 目标对象
     * @param source 源对象
     */
    checkForDuplicateKeys(target: string) {
        if (componentMap.hasOwnProperty(target)) {
            throw new Error(`[TCG_PLUGIN] 请注意！行为名有重复！！！⚠️⚠️⚠️: ${target}`);
        }
    }

    /**
     * 获取装饰器的属性
     * {"name":"startFace","version":"1"}
     * 
     * @param node 
     */
    getDecoratorProperty(node: ts.Node): { [key: string]: any } {
        let retObj: { [key: string]: any } = {}
        let decoratorExpr = (node as ts.Decorator).expression as ts.CallExpression;
        if (!decoratorExpr) {
            logger(`decoratorExpr 不存在：${decoratorExpr}`);
            return retObj;
        }
        let args = decoratorExpr.arguments;
        if (!args) {
            return retObj;
        }
        for (let j = 0; j < args.length; j++) {
            const arg = args[j] as ts.JsxAttributes;
            // 初始化 retObj[methodName] 为对象

            //遍历属性
            arg.properties.forEach(property => {
                // logger(`property arg: ${JSON.stringify(property, null, 2)}`);
                let param = property as unknown as ts.ParameterDeclaration
                // logger(`param arg: ${JSON.stringify(param, null, 2)}`);
                // logger(`方法注解内容： ${this.getNameTextByIdentifier(param.name)} = ${(param.initializer as unknown as ts.JsxText).text}`)
                let paramName = this.getNameTextByIdentifier(param.name);
                if (!retObj[paramName]) {
                    retObj[paramName] = "";
                }
                retObj[paramName] = (param.initializer as unknown as ts.JsxText).text;
            })
        }

        // logger(`last param arg: ${JSON.stringify(retObj, null, 2)}`);
        return retObj;
    }

    /**
     * 获取装饰器字符串
     * @param node 节点
     * @returns 
     */
    getDecoratorString(node: ts.Node): string {
        let decoratorExpr = (node as ts.Decorator).expression;
        // logger(`decoratorExpr:  ${JSON.stringify(decoratorExpr, null, 2)}`)
        if (!decoratorExpr || !ts.isCallExpression(decoratorExpr)) {
            return "";
        }
        // logger(`decoratorExpr && ts.isCallExpression(decoratorExpr) is true`)
        let callExpr = decoratorExpr as ts.CallExpression;
        // logger(`callExpr:  ${JSON.stringify(callExpr, null, 2)}`)
        return this.getNameTextByIdentifier(callExpr.expression);
    }

    /**
     * 从 Identifier 中获取 Text
     * @param node 
     * @returns 
     */
    getNameTextByIdentifier(node: ts.Node | undefined): string {
        if (!node) {
            return "";
        }
        return (node as ts.Identifier).escapedText.toString();
    }
}

/**
 * 
 * @param msg 日志 msg
 */
function logger(msg: string) {
    console.log(`[TGC_PLUGIN]${msg}`)
}

/**
 * Debug 下的配置文件
 */
const debugConfig = {
    "scanDir": "",
    "moduleName": "",
    "modulePath": ""
}

function debug() {
    logger(`Debug 已开启，正在执行插件：`)
    pluginExec(debugConfig)
}

/**
 * 自定义参数
 */
const customArgs = process.argv.slice(2);

/**
 * Debug 状态
 */
let is_debug = customArgs.indexOf("--debug") != -1;
logger(`Process customArgs: ${customArgs} ${is_debug}`);
if (is_debug) {
    debug();
}